package com.spring.demo1;

import com.spring.demo1.eg1.ServiceA;
import com.spring.demo1.eg1.ServiceB;
import com.spring.demo1.eg2.Service1;
import com.spring.demo1.eg2.Service2;
import com.spring.demo1.eg2.MainConfig2;
import com.spring.demo1.eg3.MainConfig3;
import org.junit.Test;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;

/**
 * @author vhans
 */
@ComponentScan
public class CycleTest {
    @Test
    public void test1() {
        DefaultListableBeanFactory factory = new DefaultListableBeanFactory();

        factory.registerBeanDefinition("serviceA", BeanDefinitionBuilder
                .genericBeanDefinition(ServiceA.class)
                .setAutowireMode(AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE)
                .getBeanDefinition());
        factory.registerBeanDefinition("serviceB", BeanDefinitionBuilder
                .genericBeanDefinition(ServiceB.class)
                .setScope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)  // serviceB为多例
                .setAutowireMode(AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE)
                .getBeanDefinition());

        // serviceA单例 serviceB多例 只能获取单例serviceA

        // 第一次获取serviceB报错
//        System.out.println(factory.getBean("serviceB"));

        // 可以获取serviceA
        ServiceA serviceA = (ServiceA) factory.getBean("serviceA");
        System.out.println(serviceA);
        System.out.println(serviceA.getServiceB());

        /**
         * eg:
         *     serviceA: 单例
         *     serviceB: 多例
         * 若使用构造器的方式相互注入，是无法完成注入操作的，会报错
         * 若采用set方式注入，所有bean都还未创建的情况下，若去容器中获取serviceB，会报错，为什么？我
         * 们来看一下过程：
         *     1.从容器中获取serviceB
         *     2.serviceB由于是多例的，所以缓存中肯定是没有的
         *     3.检查serviceB是在正在创建的bean名称列表中，没有
         *     4.准备创建serviceB
         *     5.将serviceB放入正在创建的bean名称列表中
         *     6.实例化serviceB（由于serviceB是多例的，所以不会提前暴露，必须是单例的才会暴露）
         *     7.准备填充serviceB属性，发现需要注入serviceA
         *     8.从容器中查找serviceA
         *     9.尝试从3级缓存中找serviceA，找不到
         *     10.准备创建serviceA
         *     11.将serviceA放入正在创建的bean名称列表中
         *     12.实例化serviceA
         *     13.由于serviceA是单例的，将早期serviceA暴露出去，丢到第3级缓存中
         *     14.准备填充serviceA的属性，发现需要注入serviceB
         *     15.从容器中获取serviceB
         *     16.先从缓存中找serviceB，找不到
         *     17.检查serviceB是在正在创建的bean名称列表中,发现已经存在了，抛出循环依赖的异常
         */

        /**
         * 若采用set方式注入，所有bean都还未创建的情况下，若去容器中获取serviceA:
         * 1.从容器中获取serviceA
         * 2.尝试从3级缓存中找serviceA，找不到
         * 3.准备创建serviceA
         * 4.将serviceA放入正在创建的bean名称列表中
         * 5.实例化serviceA
         * 6.由于serviceA是单例的，将早期serviceA暴露出去，丢到第3级缓存中
         * 7.准备填充serviceA的属性，发现需要注入serviceB
         * 8.从容器中获取serviceB
         * 9.serviceB由于是多例的，所以缓存中肯定是没有的
         * 10.准备创建serviceB
         * 11.将serviceB放入正在创建的bean名称列表中
         * 12.实例化serviceB（由于serviceB是多例的，所以不会提前暴露，必须是单例的才会暴露）
         * 13.准备填充serviceB属性，发现需要注入serviceA
         * 14.从容器中查找serviceA
         * 15.在3级缓存中找到serviceA，销毁并丢到2级缓存中
         * 16.通过serviceB的setServiceA注入serviceA
         * 17.完成serviceB的创建
         * 18.将serviceB返回给serviceA
         * 19.完成serviceA的创建
         */
    }

    @Test
    public void test2() {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        //创建一个BeanFactoryPostProcessor：BeanFactory后置处理器
        context.addBeanFactoryPostProcessor(beanFactory -> {
            if (beanFactory instanceof DefaultListableBeanFactory) {
                //将allowRawInjectionDespiteWrapping设置为true，可以允许早期暴露给别人使用的bean是否和最终的bean不一样
                ((DefaultListableBeanFactory) beanFactory).setAllowRawInjectionDespiteWrapping(true);
            }
        });
        context.register(MainConfig2.class);
        context.refresh();

        System.out.println("容器初始化完毕");

        //获取service1
        Service1 service1 = context.getBean(Service1.class);
        //获取service2
        Service2 service2 = context.getBean(Service2.class);

        System.out.println("----A-----");
        service2.m1();
        System.out.println("----B-----");
        service1.m1();
        System.out.println("----C-----");
        System.out.println(service2.getService1() == service1); // 这里早期暴露给别人使用的bean是否和最终的bean不一样
    }

    @Test
    public void test3() {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig3.class);
        System.out.println("容器初始化完毕");

        //获取service1
        com.spring.demo1.eg3.Service1 service1 = context.getBean(com.spring.demo1.eg3.Service1.class);
        //获取service2
        com.spring.demo1.eg3.Service2 service2 = context.getBean(com.spring.demo1.eg3.Service2.class);

        System.out.println("----A-----");
        service2.m1();
        System.out.println("----B-----");
        service1.m1();
        System.out.println("----C-----");
        System.out.println(service2.getService1() == service1); // 这里早期暴露给别人使用的bean是否和最终的bean一样
    }

}
